// This file contains material supporting section 3.7 of the textbook:
// "Object Oriented Software Engineering" and is issued under the open-source
// license found at www.lloseng.com

package com.lloseng.ocsf.client;

import java.io.*;
import java.net.*;
import java.util.*;

/**
 * The <code> AbstractClient </code> contains all the
 * methods necessary to set up the client side of a client-server
 * architecture.  When a client is thus connected to the
 * server, the two programs can then exchange <code> Object </code>
 * instances.<p>
 *
 * Method <code> handleMessageFromServer </code> must be defined by
 * a concrete subclass. Several other hook methods may also be
 * overriden.<p>
 *
 * Several public service methods are provided to
 * application that use this framework.<p>
 *
 * Project Name: OCSF (Object Client-Server Framework)<p>
 *
 * @author Dr. Robert Lagani&egrave;re
 * @author Dr. Timothy C. Lethbridge
 * @author Fran&ccedil;ois  B&eacutel;langer
 * @author Paul Holden
 * @version February 2001 (2.12)
 */
public abstract class AbstractClient implements Runnable
{
    
// INSTANCE VARIABLES ***********************************************
    
    /**
     * Sockets are used in the operating system as channels
     * of communication between two processes.
     * @see java.net.Socket
     */
    private Socket clientSocket;
    
    /**
     * The stream to handle data going to the server.
     */
    private ObjectOutputStream output;
    
    /**
     * The stream to handle data from the server.
     */
    private ObjectInputStream input;
    
    /**
     * The thread created to read data from the server.
     */
    private Thread clientReader;
    
    /**
     * Indicates if the thread is ready to stop.
     * Needed so that the loop in the run method knows when to stop
     * waiting for incoming messages.
     */
    private boolean readyToStop= false;
    
    /**
     * The server's host name.
     */
    private String host;
    
    /**
     * The port number.
     */
    private int port;
    
// CONSTRUCTORS *****************************************************
    
    /**
     * Constructs the client.
     *
     * @param  host  the server's host name.
     * @param  port  the port number.
     */
    public AbstractClient (String host, int port)
    {
        // Initialize variables
        this.host = host;
        this.port = port;
    }
    
// INSTANCE METHODS *************************************************
    
    /**
     * Opens the connection with the server.
     * If the connection is already opened, this call has no effect.
     *
     * @exception IOException if an I/O error occurs when opening.
     */
    final public void openConnection () throws IOException
    {
        // Do not do anything if the connection is already open
        if(isConnected ())
            return;
        
        //Create the sockets and the data streams
        try
        {
            clientSocket= new Socket (host, port);
            output = new ObjectOutputStream (clientSocket.getOutputStream ());
            input = new ObjectInputStream (clientSocket.getInputStream ());
        }
        catch (IOException ex)
        // All three of the above must be closed when there is a failure
        // to create any of them
        {
            try
            {
                closeAll ();
            }
            catch (Exception exc)
            { }
            
            throw ex; // Rethrow the exception.
        }
        
        clientReader = new Thread (this);  //Create the data reader thread
        readyToStop = false;
        clientReader.start ();  //Start the thread
    }
    
    /**
     * Sends an object to the server. This is the only way that
     * methods should communicate with the server.
     *
     * @param msg   The message to be sent.
     * @exception IOException if an I/O error occurs when sending
     */
    final public void sendToServer (Object msg) throws IOException
    {
        if (clientSocket == null || output == null)
            throw new SocketException ("socket does not exist");
        
        output.writeObject (msg);
    }
    
    /**
     * Closes the connection to the server.
     *
     * @exception IOException if an I/O error occurs when closing.
     */
    final public void closeConnection () throws IOException
    {
        // Prevent the thread from looping any more
        readyToStop= true;
        
        try
        {
            closeAll ();
        }
        finally
        {
            // Call the hook method
            connectionClosed ();
        }
    }
    
// ACCESSING METHODS ------------------------------------------------
    
    /**
     * @return true if the client is connnected.
     */
    final public boolean isConnected ()
    {
        return clientReader!=null && clientReader.isAlive ();
    }
    
    /**
     * @return the port number.
     */
    final public int getPort ()
    {
        return port;
    }
    
    /**
     * Sets the server port number for the next connection.
     * The change in port only takes effect at the time of the
     * next call to openConnection().
     *
     * @param port the port number.
     */
    final public void setPort (int port)
    {
        this.port = port;
    }
    
    /**
     * @return the host name.
     */
    final public String getHost ()
    {
        return host;
    }
    
    /**
     * Sets the server host for the next connection.
     * The change in host only takes effect at the time of the
     * next call to openConnection().
     *
     * @param host the host name.
     */
    final public void setHost (String host)
    {
        this.host = host;
    }
    
    /**
     * returns the client's description.
     *
     * @return the client's Inet address.
     */
    final public InetAddress getInetAddress ()
    {
        return clientSocket.getInetAddress ();
    }
    
// RUN METHOD -------------------------------------------------------
    
    /**
     * Waits for messages from the server. When each arrives,
     * a call is made to <code>handleMessageFromServer()</code>.
     * Not to be explicitly called.
     */
    final public void run ()
    {
        connectionEstablished ();
        
        // The message from the server
        Object msg;
        
        // Loop waiting for data
        
        try
        {
            while(!readyToStop)
            {
                // Get data from Server and send it to the handler
                // The thread waits indefinitely at the following
                // statement until something is received from the server
                msg = input.readObject ();
                
                // Concrete subclasses do what they want with the
                // msg by implementing the following method
                handleMessageFromServer (msg);
            }
        }
        catch (Exception exception)
        {
            if(!readyToStop)
            {
                try
                {
                    closeAll ();
                }
                catch (Exception ex)
                { }
                
                connectionException (exception);
            }
        }
        finally
        {
            clientReader = null;
        }
    }
    
// METHODS DESIGNED TO BE OVERRIDDEN BY CONCRETE SUBCLASSES ---------
    
    /**
     * Hook method called after the connection has been closed.
     * The default implementation does nothing. The method
     * may be overriden by subclasses to perform special processing
     * such as cleaning up and terminating, or attempting to
     * reconnect.
     */
    protected void connectionClosed ()
    {}
    
    /**
     * Hook method called each time an exception is thrown by the
     * client's thread that is waiting for messages from the server.
     * The method may be overridden by subclasses.
     *
     * @param exception the exception raised.
     */
    protected void connectionException (Exception exception)
    {}
    
    /**
     * Hook method called after a connection has been established.
     * The default implementation does nothing.
     * It may be overridden by subclasses to do anything they wish.
     */
    protected void connectionEstablished ()
    {}
    
    /**
     * Handles a message sent from the server to this client.
     * This MUST be implemented by subclasses, who should respond to
     * messages.
     *
     * @param msg   the message sent.
     */
    protected abstract void handleMessageFromServer (Object msg);
    
    
// METHODS TO BE USED FROM WITHIN THE FRAMEWORK ONLY ----------------
    
    /**
     * Closes all aspects of the connection to the server.
     *
     * @exception IOException if an I/O error occurs when closing.
     */
    private void closeAll () throws IOException
    {
        try
        {
            //Close the socket
            if (clientSocket != null)
                clientSocket.close ();
            
            //Close the output stream
            if (output != null)
                output.close ();
            
            //Close the input stream
            if (input != null)
                input.close ();
        }
        finally
        {
            // Set the streams and the sockets to NULL no matter what
            // Doing so allows, but does not require, any finalizers
            // of these objects to reclaim system resources if and
            // when they are garbage collected.
            output = null;
            input = null;
            clientSocket = null;
        }
    }
}
// end of AbstractClient class
